﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System;

public enum ManagerState : byte
{
    Evolution = 0,
    PlayerChoice,
    Controllable
}

public enum PlayerPick : byte
{
    Yes = 0,
    No,
    Waiting
}

public class Manager : MonoBehaviour 
{
    private static Manager _instance;
    public static Manager Instance { get { return _instance; } private set { } }
    
    public float Timescale = 1.0f;
    public float Duration = 10.0f;
    public int Depth = 3;
    public int Genotypes = 100;
    public int MaxGenerations = 10;
    public int Trials = 3;
    public int MinSystemLength = 12;
    public int MaxSystemLength = 60;

    [HideInInspector]
    public PlayerPick Pick = PlayerPick.Waiting;

    public ManagerState State = ManagerState.Evolution;

    private float bestSoFar = 0;
    private string foldername;
    private GeneticAlgorithm GA;

    List<GenoType> _genotypes = new List<GenoType>();

    public delegate void ManagerEventHandler(float duration);
    public ManagerEventHandler ManagerEvent;

    public delegate void ControllableEventHandler(bool c);
    public ControllableEventHandler ControllableEvent;

    void Awake()
    {
        if (_instance == null)
        {
            _instance = this;
        }
    }

	void Start () 
    {
     //   StartCoroutine(Iteration());
        GUIScript.NumberCreatures = Genotypes;
        GUIScript.MaxGenerations = MaxGenerations;

        StartCoroutine(RunEvolution());      
	}

    IEnumerator RunEvolution()
    {
        Time.timeScale = Timescale;
        State = ManagerState.Evolution;
        GA = new GeneticAlgorithm(Genotypes, MinSystemLength, MaxSystemLength);

        foldername = @"Genes\" +  String.Format("{0:dd MMMM yyyy, HH.mm.ss}", DateTime.Now);
        System.IO.Directory.CreateDirectory(foldername);
        string filename = foldername + @"\all.csv";
        using (StreamWriter sw = new StreamWriter(filename))
        {
            sw.WriteLine("\"Generation\";\"Id\";\"ParentId\";\"Fitness\";\"Axiom\";\"Production Rules\";\"System\"");
        }

        int gen = 0;

        //initial run
        for (int i = 0; i < GA.Size(); i++)
        {
            Gene g = GA[i];
            GUIScript.Creature = i;
            float fitness = 0;
            g.Prepare(); // Makes sure that the Lsystem is initialized
            if (g.GenoType.System.Length > MinSystemLength && g.GenoType.System.Length < MaxSystemLength)
            {
                float[] fitnessValues = new float[Trials];
                for (int t = 0; t < Trials; t++)
                {
                    GameObject p = new GameObject("");
                    PhenoType pheno = p.AddComponent<PhenoType>();
                    pheno.Fitness = 0;
                    pheno.Init(g.GenoType.System);

                    yield return new WaitForSeconds(Duration);
                    fitnessValues[t] = pheno.GetFitness();

                    pheno.DestroyWackimal();
                    Destroy(p);
                }

                //Pick the lowest fitness so we dont get exploding values
                fitness = -1;
                int number = 0;
                float currentFitness = 0;

                for (int f = 0; f < Trials; f++ )
                {
                    if (fitnessValues[f] > 0 && fitnessValues[f] < 2000.0f)
                    {
                        currentFitness += fitnessValues[f];
                        number++;
                        break;
                    }
                }

                fitness = currentFitness / number;
                g.Fitness = currentFitness;             
            }
            else
            {
                g.Fitness = -1; // We do not want creatures too big or small
            }
            GUIScript.LastFitness = g.Fitness;
            g.AppendCSV(gen, filename);               
        }

        int startIndex = GA.Size() / 5 * 3;

        while (true)
        {
            GUIScript.Generation = gen;

            int badGenes = 0;
            // Evaluate generation
            for (int i = startIndex; i < GA.Size(); i++)
            {
                Gene g = GA[i];
                GUIScript.Creature = i;
                float fitness = 0;
                g.Prepare(); // Makes sure that the Lsystem is initialized
                if (g.GenoType.System.Length > MinSystemLength && g.GenoType.System.Length < MaxSystemLength)
                {
                    float[] fitnessValues = new float[Trials];
                    for (int t = 0; t < Trials; t++)
                    {
                        GameObject p = new GameObject("");
                        PhenoType pheno = p.AddComponent<PhenoType>();
                        pheno.Fitness = 0;
                        pheno.Init(g.GenoType.System);

                        yield return new WaitForSeconds(Duration);
                        fitnessValues[t] = pheno.GetFitness();

                        pheno.DestroyWackimal();
                        Destroy(p);
                    }

                    //Pick the lowest fitness so we dont get exploding values
                    fitness = -1;
                    int number = 0;
                    float currentFitness = 0;

                    for (int f = 0; f < Trials; f++ )
                    {
                        if (fitnessValues[f] >= 0 && fitnessValues[f] < 2000.0f)
                        {
                            currentFitness += fitnessValues[f];
                            number++;
                            break;
                        }
                        else
                            Debug.Log(fitnessValues[f] + " bad");
                    }

                    fitness = currentFitness / number;
                    g.Fitness = currentFitness;             
                }
                else
                {
                    g.Fitness = -1; // We do not want creatures too big or small
                    badGenes++;
                }
                GUIScript.LastFitness = g.Fitness;
                g.AppendCSV(gen, filename);
                
            } 
            // Done evaluating generation

            // Stat counting
            float avgFitness = 0f;
            float maxFitness = float.NegativeInfinity, minFitness = float.PositiveInfinity;
            string best = "", worst = "";

            for (int i = 0; i < GA.Size(); i++)
            {
                float curFitness = GA.GetGene(i).Fitness;
                if (curFitness < minFitness)
                {
                    minFitness = curFitness;
                    worst = GA.GetGene(i).GenoType.System;
                }
                if (curFitness > maxFitness)
                {
                    maxFitness = curFitness;
                    best = GA.GetGene(i).GenoType.System;
                }
            }

            avgFitness = GA.AverageFitness();
            string output = "Generation: " + gen;
            output += "\t AvgFitness: " + avgFitness;
            output += "\t MinFitness: " + minFitness + " (" + worst + ")";
            output += "\t MaxFitness: " + maxFitness + " (" + best + ")";

            GenerationInfo info = new GenerationInfo();
            info.Generation = gen;
            info.AverageFitness = avgFitness;
            info.MaxFitness = maxFitness;
            info.MinFitness = minFitness;
            info.BadGenes = badGenes;

            GUIScript.Info.Add(info);

           // print(output);
            // End stat counting

            GA.NextGeneration();

            gen++;
            if (gen > MaxGenerations)
            {
                break;
            }
        }

        SaveResultsToFile();

        //now let players choose
        State = ManagerState.PlayerChoice;
        Time.timeScale = 2;

        int numbersOfCreatures = GA.Size();

        List<Gene> favoriteSystems = new List<Gene>();

        //bool addPhenotype = true;
        KeyCode[] keys = new KeyCode[] { KeyCode.Alpha1, KeyCode.Alpha2, KeyCode.Alpha3, KeyCode.Alpha4, KeyCode.Alpha5, KeyCode.Alpha6, KeyCode.Alpha7, KeyCode.Alpha8, KeyCode.Alpha9 };

        for (int p = GA.Size() - 1; p > GA.Size() - numbersOfCreatures; p--)
        {
            GameObject g = new GameObject("");
            PhenoType pheno = g.AddComponent<PhenoType>();
            pheno.Init(GA.GetGene(p).GenoType.System);
            List<GameObject> wackimalParts = pheno.GetParts();

            int index = 0;

            for(int i = 1; i < wackimalParts.Count; i++)
            {
                if(index >= keys.Length)
                    break;

                ConfigurableJoint joint = wackimalParts[i].GetComponent<ConfigurableJoint>();

                if (joint.angularYMotion == ConfigurableJointMotion.Locked && joint.angularZMotion == ConfigurableJointMotion.Locked)
                    continue;

                wackimalParts[i].GetComponent<PhysicsController>().SetKeyCode(keys[index]);
                index++;
            }

            for (int k = 0; k < 40000; k++)
            {                
                if (Pick == PlayerPick.Yes)
                {
                    favoriteSystems.Add(GA.GetGene(p));
                    break;
                }
                else if (Pick == PlayerPick.No)
                    break;

                yield return new WaitForSeconds(0.2f);
            }

            pheno.DestroyWackimal();
            Destroy(g);
            Pick = PlayerPick.Waiting;
            State = ManagerState.PlayerChoice;
        }

        //Save picked wackimal genes
        SavePlayerChoosenCreaturesToFile(favoriteSystems);
    }

    private void SaveResultsToFile()
    {
        using (StreamWriter sw = new StreamWriter(foldername + @"\results.csv"))
        {
            sw.WriteLine("\"Generation\";\"Min\";\"Avg\";\"Max\";\"Bad Genes\"");

            foreach (GenerationInfo info in GUIScript.Info)
            {
                sw.WriteLine(string.Format("\"{0}\";\"{1}\";\"{2}\";\"{3}\";\"{4}\"", info.Generation, info.MinFitness.ToString("0.00").Replace('.', ','),
                    info.AverageFitness.ToString("0.00").Replace('.', ','), info.MaxFitness.ToString("0.00").Replace('.', ','), info.BadGenes));
            }
        }
    }

    private void SavePlayerChoosenCreaturesToFile(List<Gene> genes)
    {
        using (StreamWriter sw = new StreamWriter(foldername + @"\playerchoices.csv"))
        {
            sw.WriteLine("\"Generation\";\"Id\";\"ParentId\";\"Fitness\";\"Axiom\";\"Production Rules\";\"System\"");
        }

        foreach (Gene g in genes)
        {
            g.AppendCSV(0, foldername + @"\playerchoices.csv");
        }
    }

    IEnumerator Iteration()
    {
        Time.timeScale = Timescale;
        
        while(_genotypes.Count < Genotypes)
        {
            GenoType g = new GenoType(3);

            if (g.System.Length > MinSystemLength && g.System.Length < MaxSystemLength)
            {
                GameObject p = new GameObject("");
                PhenoType pheno = p.AddComponent<PhenoType>();
                pheno.Init(g.System);

                yield return new WaitForSeconds(Duration);

                if (pheno.Fitness < 2000.0f)
                {
                    g.Fitness = pheno.Fitness; ;
                    pheno.DestroyWackimal();
                    Destroy(p);

                    _genotypes.Add(g);
                }
            }
        }

        SortGenotypes(ref _genotypes);

        foreach (GenoType geno in _genotypes)
        {
            Debug.Log(geno.Fitness);
        }
    }

    private void SortGenotypes(ref List<GenoType> list)
    {
        list.Sort(delegate(GenoType g1, GenoType g2) { return g1.Fitness.CompareTo(g2.Fitness); });
    }

    void Dummy() { }
}
